Magento 2: CRUD Models for Database Access

Create Module

ulsestorm_ToDoCrud module

Generating Crud Files

As previously mentioned, we’re going to create a model for a “To Do” item in our imaginary productivity application. We’ll want this model to have two main fields — the text of the to do item, and a date completed field.

To generate the base files needed for this module, run the following generate_crud_model command

$ pestle.phar generate_crud_model Pulsestorm_ToDoCrud TodoItem
Creating: /path/to/magento2/app/code/Pulsestorm/ToDoCrud/Model/TodoItemInterface.php
Creating: /path/to/magento2/app/code/Pulsestorm/ToDoCrud/Model/ResourceModel/TodoItem/Collection.php
Creating: /path/to/magento2/app/code/Pulsestorm/ToDoCrud/Model/ResourceModel/TodoItem.php
Creating: /path/to/magento2/app/code/Pulsestorm/ToDoCrud/Model/TodoItem.php
Creating: /path/to/magento2/app/code/Pulsestorm/ToDoCrud/Setup/InstallSchema.php
Creating: /path/to/magento2/app/code/Pulsestorm/ToDoCrud/Setup/InstallData.php

nce we’ve created the class files, our next step is creating the database table for our model.

To add the database table, we’ll use the Install Schema feature of Magento’s ORM. Pestle has already created a boiler plate install schema class at the following location

<?php
namespace PulsestormToDoCrudSetup;
use MagentoFrameworkSetupInstallSchemaInterface;
use MagentoFrameworkSetupSchemaSetupInterface;
use MagentoFrameworkSetupModuleContextInterface;
class InstallSchema implements InstallSchemaInterface
{
public function install(MagentoFrameworkSetupSchemaSetupInterface $installer, MagentoFrameworkSetupModuleContextInterface $context)
{
//…
//START table setup
$installer->startSetup();
$table = $installer->getConnection()->newTable(
$installer->getTable(‘pulsestorm_todocrud_todoitem’)
)->addColumn(
‘pulsestorm_todocrud_todoitem_id’,
MagentoFrameworkDBDdlTable::TYPE_INTEGER,
null,
array(
‘identity’ => true, ‘nullable’ => false, ‘primary’ => true, ‘unsigned’ => true,
),
‘Entity ID’
)->addColumn(
‘title’,
MagentoFrameworkDBDdlTable::TYPE_TEXT,
255,
array(
‘nullable’ => false,
),
‘Demo Title’
)->addColumn(
‘creation_time’,
MagentoFrameworkDBDdlTable::TYPE_TIMESTAMP,
null,
array(),
‘Creation Time’
)->addColumn(
‘update_time’,
MagentoFrameworkDBDdlTable::TYPE_TIMESTAMP,
null,
array(),
‘Modification Time’
)->addColumn(
‘is_active’,
MagentoFrameworkDBDdlTable::TYPE_SMALLINT,
null,
array(
‘nullable’ => false, ‘default’ => ‘1’,
),
‘Is Active’
);
//…
$installer->getConnection()->createTable($table);
$installer->endSetup();
}
}
?>

his code creates a PHP data structure that represents a MySQL table. Magento will automatically run this install schema class when we run bin/magento setup:upgrade. You may see this install schema class referred to as amigration, or by its name in Magento 1 — a setup resource class.

Pestle creates a stock database table migration for you. This stock migration will create a table namedpulsestorm_todocrud_todoitem, with the following columns

pulsestorm_todocrud_todoitem_id
title
creation_time
update_time
is_active

The first column is the table’s primary key, and will serve as the model’s ID. Pestle bases its name on the name of the table.

The second column, title, is optional, and not used my Magento’s ORM.

The last three columns (creation_time, update_time, and is_active) are fields that Magento expects to find in a model. While not strictly required, having these fields in your models is always a good idea.

Running a Magento 2 Migration

In Magento 1, the core system code automatically ran any needed migrations whenever an uncached HTTP(S) request was made. When it worked, this feature was super useful, as the simple act of adding a module to the system also automatically added its data tables. Unfortunately, when this didn’t work, it could leave the system in a half updated state that was hard to recover from.

In Magento 2, a system owner is required to run the setup:upgrade command when they want to run a migration. Let’s give that a try with the above code in our system. Run the following

php bin/magento setup:upgrade

and you should see the pulsestorm_todocrud_todoitem table created in your database.

mysql> show create table pulsestorm_todocrud_todoitemG;
*************************** 1. row ***************************
       Table: pulsestorm_todocrud_todoitem
Create Table: CREATE TABLE `pulsestorm_todocrud_todoitem` (
  `pulsestorm_todocrud_todoitem_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Entity ID',
  `item_text` varchar(255) NOT NULL COMMENT 'Text of the to do item',
  `date_completed` datetime DEFAULT NULL COMMENT 'Date the item was completed',
  `creation_time` timestamp NULL DEFAULT NULL COMMENT 'Creation Time',
  `update_time` timestamp NULL DEFAULT NULL COMMENT 'Modification Time',
  `is_active` smallint(6) NOT NULL DEFAULT '1' COMMENT 'Is Active',
  PRIMARY KEY (`pulsestorm_todocrud_todoitem_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='pulsestorm_todocrud_todoitem'
1 row in set (0.00 sec)    

If your migration did not run, it may be because you ran setup:upgrade before you added the install schema class. If this is the case, you’ll want to remove the information that lets Magento know thePulsestorm_ToDoCrud module is installed in the system. You can find this information in the setup_moduletable.

mysql> select * from setup_module where module = 'Pulsestorm_ToDoCrud';
+---------------------+----------------+--------------+
| module              | schema_version | data_version |
+---------------------+----------------+--------------+
| Pulsestorm_ToDoCrud | 0.0.1          | 0.0.1        |
+---------------------+----------------+--------------+
1 row in set (0.00 sec)

If you delete this row

mysql> DELETE from setup_module where module = 'Pulsestorm_ToDoCrud';

and try running setup:upgrade again, Magento should call the install method and install your table.

The InstallSchema.php file is meant to hold code for creating the structure of your database tables. If you wanted to install actual data into the tables you’ve created, you’d use the InstallData.php file

app/code/Pulsestorm/ToDoCrud/Setup/InstallData.php

The InstallData.php file is beyond the scope of this article, but take a look at some core modules to get an idea for how you’d use it

$ find vendor/magento/ -name InstallData.php
vendor/magento//magento2-base/dev/tests/api-functional/_files/Magento/TestModuleIntegrationFromConfig/Setup/InstallData.php
vendor/magento//module-authorization/Setup/InstallData.php
vendor/magento//module-bundle/Setup/InstallData.php
//...
vendor/magento//module-widget-sample-data/Setup/InstallData.php
vendor/magento//module-wishlist-sample-data/Setup/InstallData.php    

A Base Magento 2 CRUD Model

Now that we’ve got a table definition installed, lets take a look at the other four files pestle created for us.

First is the base model file

#File: app/code/Pulsestorm/ToDoCrud/Model/TodoItem.php
<?php
namespacePulsestormToDoCrudModel;
classTodoItem extendsMagentoFrameworkModelAbstractModel implementsTodoItemInterface, MagentoFrameworkDataObjectIdentityInterface
{
    constCACHE_TAG = 'pulsestorm_todocrud_todoitem';
    protectedfunction_construct()
    {
        $this->_init('PulsestormToDoCrudModelResourceModelTodoItem');
    }
    publicfunctiongetIdentities()
    {
        return[self::CACHE_TAG . '_'. $this->getId()];
    }
}
This is our main model class file. Objects instantiated with this class are the bread and butter for Magento 2 CRUD programming. Like Magento 1, all Magento CRUD models extend the base abstract model class.
MagentoFrameworkModelAbstractModel

Unlike Magento 1, all CRUD models also implement an IdentityInterface. This interface forces model developers to define a getIdentities method

<?php
#File: vendor/magento/framework/DataObject/IdentityInterface.php
namespace MagentoFrameworkDataObject;
interface IdentityInterface
{
    public function getIdentities();
}
You’ll also notice the model implements a
Pulsestorm/ToDoCrud/Model/TodoItemInterface class.
#File: app/code/Pulsestorm/ToDoCrud/Model/TodoItemInterface.php
<?php
namespace PulsestormToDoCrudModel;
interface TodoItemInterface
{
 
}
While not strictly necessary, the model specific TodoItemInterface interface plays an important role when it comes time to exporting CRUD models to Magento’s new service contracts based API. While beyond the scope of this article, the model specific interfaces for Magento CRUD models will determine which class methods are available via the Magento API.

The last thing to make note of is the _construct method

#File: app/code/Pulsestorm/ToDoCrud/Model/TodoItem.php
protected function _construct()
{
    $this->_init('PulsestormToDoCrudModelResourceModelTodoItem');
}

A model’s _construct method is a leftover concept from Magento 1. It’s an alternative constructor. The implementation of this _construct method is beyond the scope of this article. All you need to know is that_construct will be called whenever a model is instantiated. Every CRUD model in Magento must use the_construct method to call the _init method. The _init method accepts a single string parameter — the name of this model’s resource model.

A Magento 2 Resource Model

In Magento 2, the model class defines the methods an end-user-programmer will use to interact with a model’s data. A resource model class contains the methods that will actually fetch the information from the database. Each CRUD model in Magento 2 has a corresponding resource model class.

#File: app/code/Pulsestorm/ToDoCrud/Model/ResourceModel/TodoItem.php
<?php
namespace PulsestormToDoCrudModelResourceModel;
class TodoItem extends MagentoFrameworkModelResourceModelDbAbstractDb
{
    protected function _construct()
    {
        $this->_init('pulsestorm_todocrud_todoitem','pulsestorm_todocrud_todoitem_id');
    }
}

Every CRUD resource model class extends theMagentoFrameworkModelResourceModelDbAbstractDb class. This base class contains the basic logic for fetching information from a single database table.

For a basic model like ours, the only thing a resource model must do is call the _init method from _construct. The _init method for a resource model accepts two arguments. The first is the name of the database table (pulsestorm_todocrud_todoitem), and the second is the ID column for the model (pulsestorm_todocrud_todoitem_id).

While it’s beyond the scope of this article, Magento 2’s active record implementation contains no method for linking tables via primary keys. How to use multiple database tables is up to each individual module developer, and a resource model will typically contain the SQL generating methods needed to fetch information from related tables.

A Magento 2 Collection Model

With a model and resource model, you have everything you need to fetch and save individual models into the database. However, there are times where you’ll want to fetch multiple models of a particular type. To solve this problem, every CRUD model in Magento 2 has a corresponding resource model collection. A collection collectsindividual models. It’s considered a resource model since it builds the SQL code necessary to pull information from a database table.

#File: app/code/Pulsestorm/ToDoCrud/Model/ResourceModel/TodoItem/Collection.php
<?php
namespace PulsestormToDoCrudModelResourceModelTodoItem;
class Collection extends MagentoFrameworkModelResourceModelDbCollectionAbstractCollection
{
    protected function _construct()
    {
        $this->_init('PulsestormToDoCrudModelTodoItem','PulsestormToDoCrudModelResourceModelTodoItem');
    }
}

All collections in Magento 2 extend the baseMagentoFrameworkModelResourceModelDbCollectionAbstractCollection collection class. Like a model and resource model, a collection resource model must call the _init method. A collection resource model’s _init method accepts two arguments. The first is the model that this collection collects. The second is that collected model’s resource model.

Using a Crud Model

Now that we’ve explored what each of the 6 files created by pestle’s generate_crud_model do, we’re ready to get into using the CRUD models.

First, let’s make sure the view we setup earlier is working. Open the following file and add the following var_dumpto the _prepareLayout method.

#File: app/code/Pulsestorm/ToDoCrud/Block/Main.php
 
<?php
namespace PulsestormToDoCrudBlock;
class Main extends MagentoFrameworkViewElementTemplate
{
    function _prepareLayout()
    {
        var_dump("I am Here");
        exit;
    }
}

We’re going to write our code in the _prepareLayout method, and then halt execution. While this is something you’d never do in a finished module, working out code samples in this sort of environment is common practice when learning and developing If you load the endpoint we created earlier

http://magento.example.com/pulsestorm_todocrud

and you should see your I am Here text on a white screen.

The first thing we’re going to do is instantiate a new To Do Item model, set its text, and then save it. In Magento 1, we’d use code that looked something like this

function _prepareLayout()
{
    $model = Mage::getModel('pulsestorm_todocrud/todoitem')
    ->setItemText('Finish my Magento Article')
    ->save();
}

However, Magento 2 no longer uses static factory methods on a global Mage class. Instead, we need to use twonew Magento OO systems.

  1. We’ll use automatic constructor dependency injection to …
  2. … inject a factory object, and then use the factory object to instantiate our CRUD model

If you’re not familiar with automatic constructor dependency injection, you’ll want to work your way through ourMagento 2 object manager series. If you’re not familiar with Factory objects — that’s because we haven’t covered them yet!

Magento 2 Factory Objects

In object oriented programming, a factory method is a method that’s used to instantiate an object. Factory methods exist to ensure system developers have control over how a particular object is instantiated, and how its arguments are passed in. There’s a certain school of though that thinks direct use of the new keyword in programming

1
$object = new Foo;

is an anti-pattern, as directly instantiating an object creates a hard coded dependency in a method. Factory methods give the system owner the ability to control which objects are actually returned in a given context.

A factory object serves a similar purpose. In Magento 2, each CRUD model has a corresponding factory class. All factory class names are the name of the model class, appended with the word “Factory”. So, since our model class is named

Pulsestorm/ToDoCrud/Model/TodoItem

this means our factory class is named

Pulsestorm/ToDoCrud/Model/TodoItemFactory

To get an instance of the factory class, replace your block class with the following.

#File: app/code/Pulsestorm/ToDoCrud/Block/Main.php   
<?php
namespace PulsestormToDoCrudBlock;
class Main extends MagentoFrameworkViewElementTemplate
{
    protected $toDoFactory;
    public function __construct(
        MagentoFrameworkViewElementTemplateContext $context,
        PulsestormToDoCrudModelTodoItemFactory $toDoFactory
    )
    {
        $this->toDoFactory = $toDoFactory;
        parent::__construct($context);
    }
 
    function _prepareLayout()
    {
        var_dump(
            get_class($this->toDoFactory)
        );
        exit;
    }
}

What we’ve done here is use automatic constructor dependency injection to inject aPulsestormToDoCrudModelTodoItemFactory factory object, and assign it to the toDoFactory object property in the constructor method.

1
2
3
4
5
6
7
8
9
10
#File: app/code/Pulsestorm/ToDoCrud/Block/Main.php       
protected $toDoFactory;
public function __construct(
    MagentoFrameworkViewElementTemplateContext $context,
    PulsestormToDoCrudModelTodoItemFactory $toDoFactory
)
{
    $this->toDoFactory = $toDoFactory;
    parent::__construct($context);
}

We also had to inject a block context object and pass that to our parent constructor. We’ll cover these context object in future articles, but if you’re curious about learning more, this quickies post is a good place to start.

In addition to injecting a factory into our block, we also added the following to our _prepareLayout method

1
2
3
4
5
6
7
8
#File: app/code/Pulsestorm/ToDoCrud/Block/Main.php   
function _prepareLayout()
{
    var_dump(
        get_class($this->toDoFactory)
    );
    exit;
}

This will dump the toDoFactory’s class name to the screen, and is a quick sanity check that our automatic constructor dependency injection worked. Reload your page with the above in place, and you should see the following

string 'PulsestormToDoCrudModelTodoItemFactory' (length=41)

Next, replace your _prepareLayout method with this code

1
2
3
4
5
6
7
8
9
10
#File: app/code/Pulsestorm/ToDoCrud/Block/Main.php   
function _prepareLayout()
{
    $todo = $this->toDoFactory->create();
    $todo->setData('item_text','Finish my Magento article')
    ->save();
    var_dump('Done');
    exit;
}

This code calls the create method of our factory. This will instantiate aPulsestormToDoCrudModelTodoItemFactory object for us. Then, we set the item_text property of our model, and call its save method. Reload your page to run the above code, and then check your database table

mysql> select * from pulsestorm_todocrud_todoitemG
*************************** 1. row ***************************
pulsestorm_todocrud_todoitem_id: 1
                      item_text: Finish my Magento article
                 date_completed: NULL
                  creation_time: NULL
                    update_time: NULL
                      is_active: 1
1 row in set (0.00 sec)

You’ll find that Magento has saved the information you requested. If you wanted to fetch this specific model again, you’d use code that looked like the following.

1
2
3
4
5
6
7
8
9
#File: app/code/Pulsestorm/ToDoCrud/Block/Main.php       
function _prepareLayout()
{
    $todo = $this->toDoFactory->create();
    $todo = $todo->load(1);       
    var_dump($todo->getData());
    exit;
}

Here we’ve used the factory to create our model, used the model’s load method to load a model with the ID of 1, and then dumped the model’s data using the various magic setter and getter methods available to a Magento 2 model.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#File: app/code/Pulsestorm/ToDoCrud/Block/Main.php       
function _prepareLayout()
{
    $todo = $this->toDoFactory->create();
    $todo = $todo->load(1);       
    var_dump($todo->getData());
    var_dump($todo->getItemText());
    var_dump($todo->getData('item_text'));
    exit;
}

Finally, if we wanted to use a CRUD model’s collection object, we’d use code that looked like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#File: app/code/Pulsestorm/ToDoCrud/Block/Main.php         
function _prepareLayout()
{
    $todo = $this->toDoFactory->create();
    $collection = $todo->getCollection();
    foreach($collection as $item)
    {
        var_dump('Item ID: ' . $item->getId());
        var_dump($item->getData());
    }
    exit;
}  

Again, this code uses a factory object to create a CRUD model object. Then, we use the CRUD model object’sgetCollection method to fetch the model’s collection. Then, we iterate over the items returned by the collection.

Once instantiated via a factory, Magento 2’s CRUD models behave very similarly, if not identically, to their Magento 1 counterparts. If you’re curious about Magento 1’s CRUD objects, our venerable Magento 1 for PHP MVC Developers article may be of interest, as well as the Varien Data Collections article.

Where did the Factory Come From

You may be thinking to yourself — how did Magento instantiate aPulsestorm/ToDoCrud/Model/TodoItemFactory class if I never defined one? Factory classes are another instance of Magento 2 using code generation (first covered in our Proxy object article). Whenever Magento’s object manager encounters a class name that ends in the word Factory, it will automatically generate the class in thevar/generation folder if the class does not already exist. You can see your generated factory class at the following location

1
2
3
4
5
6
7
8
9
10
11
#File: var/generation/Pulsestorm/ToDoCrud/Model/TodoItemFactory.php
<?php
namespace PulsestormToDoCrudModel;
/**
 * Factory class for @see PulsestormToDoCrudModelTodoItem
 */
class TodoItemFactory
{
    //...
}

 

 

 

 

 


Leave a Comment